<?php

/**
 * 图像处理
 *
 * @author chen meok23@sina.com
 * @date   2016-06-03
 */

namespace ext;

class Image
{
    /* 缩略图相关常量定义 */
    const THUMB_SCALE = 1;     // 等比例缩放
    const THUMB_CENTER = 3;    // 缩放后居中裁剪
    const THUMB_NORTHWEST = 4; // 缩放后左上角裁剪
    const THUMB_SOUTHEAST = 5; // 缩放后右下角裁剪
    const THUMB_FIXED = 6;     // 固定尺寸缩放
    const THUMB_NOT = 7;       // 尺寸不变

    /* 水印相关常量定义 */
    const WATER_NORTHWEST = 1; // 左上角水印
    const WATER_NORTH = 2;     // 上居中水印
    const WATER_NORTHEAST = 3; // 右上角水印
    const WATER_WEST = 4;      // 左居中水印
    const WATER_CENTER = 5;    // 居中水印
    const WATER_EAST = 6;      // 右居中水印
    const WATER_SOUTHWEST = 7; // 左下角水印
    const WATER_SOUTH = 8;     // 下居中水印
    const WATER_SOUTHEAST = 9; // 右下角水印

    public $info = array();

    protected $_image = null;

    private $_type = '';
    private $_mime = '';

    public function __construct($filename = '')
    {
        if (!empty($filename) && is_file($filename)) {
            $this->open($filename);
        }
    }

    public function __destruct()
    {
        if (!$this->_image) {
            imagedestroy($this->_image);
            $this->_image = null;
        }
    }

    /**
     * 从文件打开一个图像资源
     *
     * @param $filename 文件的路径或者说文件名
     *
     * @return bool
     * @throws Exception
     */
    public function open($filename)
    {
        if (!is_file($filename)) {
            throw new Exception("[{$filename}] is not exist.");
        }

        // 从文件路径获得图像信息
        $tmp = getimagesize($filename);
        $this->info = array(
            'width'  => $tmp[0],
            'height' => $tmp[1],
            'type'   => image_type_to_extension($tmp[2], false),
            'mime'   => $tmp['mime'],
        );

        // 获取源图像
        switch ($this->info['mime']) {
            case 'image/gif':
                $src_image = @imagecreatefromgif($filename);
                break;

            case 'image/jpeg':
                $src_image = @imagecreatefromjpeg($filename);
                break;

            case 'image/png':
                $src_image = @imagecreatefrompng($filename);
                break;

            default:
                $src_image = false;
                break;
        }

        if (false == $src_image) {
            throw new Exception("[{$filename}] is not illegal image.");
        }
        $this->_image = $src_image;

        $this->setType($this->info['type']);
    }

    /**
     * 从字符流打开一个图像资源
     *
     * @param string $content 字符流
     *
     * @return bool
     * @throws Exception
     */
    public function openFromContent($content)
    {
        // 获取源图像
        $src_image = @imagecreatefromstring($content);
        if (false == $src_image) {
            throw new Exception("The param is not image.");
        }
        $this->_image = $src_image;

        // 从文件路径获得图像信息
        $width = (int)imagesx($src_image);
        $height = (int)imagesy($src_image);
        $this->info = array(
            'width'  => $width,
            'height' => $height,
            'type'   => '',
            'mime'   => '',
        );

        $this->setType();
    }

    /**
     * 从URL打开一个图像资源
     *
     * @param string $url
     *
     * @return bool
     * @throws Exception
     */
    public function openFromUrl($url)
    {
        /* 判断URL是否可以访问 */
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url); //获取内容url
        curl_setopt($ch, CURLOPT_HEADER, 1); //获取http头信息
        curl_setopt($ch, CURLOPT_NOBODY, 1); //不返回html的body信息
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //返回数据流，不直接输出
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);       //超时时长，单位秒
        curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if (200 !== (int)$http_code) {
            throw new Exception("[$url] URL error.");
        }

        // 从文件路径获得图像信息
        $tmp = getimagesize($url);
        $this->info = array(
            'width'  => $tmp[0],
            'height' => $tmp[1],
            'type'   => image_type_to_extension($tmp[2], false),
            'mime'   => $tmp['mime'],
        );

        // 获取源图像
        $content = file_get_contents($url);
        $src_image = @imagecreatefromstring($content);
        if (false === $src_image) {
            throw new Exception("The param is not image.");
        }
        $this->_image = $src_image;

        $this->setType($this->info['type']);
    }

    /**
     * 设置图片的保存类型
     *
     * @param string $type 文件后缀
     *
     * @return int
     */
    public function setType($type = 'jpg')
    {
        $type = strtolower(trim($type));

        switch ($type) {
            case 'jpg':
            case 'jpeg':
                $type = 'jpg';
                $mime = 'image/jpeg';
                break;

            case 'png':
                $mime = 'image/png';
                break;

            case 'gif':
                $mime = 'image/gif';
                break;

            default:
                return false;
                break;
        }

        $this->_type = $type;
        $this->_mime = $mime;
    }

    public function getType()
    {
        return array(
            'type' => $this->_type,
            'mime' => $this->_mime,
        );
    }

    /**
     * @param int $src_x 裁剪区域x坐标
     * @param int $src_y 裁剪区域y坐标
     * @param int $src_w 裁剪区域宽
     * @param int $src_h 裁剪区域高
     * @param int $dst_w 保存的宽
     * @param int $dst_h 保存的高
     */
    public function crop($src_x = 0, $src_y = 0, $src_w, $src_h, $dst_w = null, $dst_h = null)
    {
        // 设置保存尺寸
        is_null($dst_w) && $dst_w = $src_w;
        is_null($dst_h) && $dst_h = $src_h;

        // 创建新图像
        $image = imagecreatetruecolor($dst_w, $dst_h);
        $color = imagecolorallocate($image, 255, 255, 255);
        imagefill($image, 0, 0, $color);

        imagecopyresampled($image, $this->_image, 0, 0, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);

        // 重置源图
        imagedestroy($this->_image);
        $this->_image = $image;

        $this->info['width'] = $dst_w;
        $this->info['height'] = $dst_h;
    }

    /**
     * 图像缩略
     *
     * @param int $dst_w 保存的宽
     * @param int $dst_h 保存的高
     * @param int $mode 缩略模式
     *
     * @throws Exception
     */
    public function thumb($dst_w, $dst_h, $mode = self::THUMB_CENTER)
    {
        $src_w = $this->info['width'];
        $src_h = $this->info['height'];

        // 原图尺寸小于缩略图尺寸则不进行缩略
        if ($src_w < $dst_w || $src_h < $dst_h) {
            $mode = self::THUMB_NOT;
        }

        switch ($mode) {

            /* 等比例缩放 */
            case self::THUMB_SCALE:
                // 计算缩放比例
                if ($dst_h == 0 || $src_w > $src_h) {
                    $scale = $dst_w / $src_w;
                } else {
                    $scale = $dst_h / $src_h;
                }

                // 设置缩略图的坐标及宽度和高度
                $src_x = $src_y = 0;
                $dst_w = $src_w * $scale;
                $dst_h = $src_h * $scale;
                break;

            /* 居中裁剪 */
            case self::THUMB_CENTER:
                // 计算缩放比例
                if ($src_w > $src_h) {
                    $scale = $dst_h / $src_h;
                } else {
                    $scale = $dst_w / $src_w;
                }

                //设置缩略图的坐标及宽度和高度
                $src_w = $dst_w / $scale;
                $src_h = $dst_h / $scale;
                $src_x = ($this->info['width'] - $src_w) / 2;
                $src_y = ($this->info['height'] - $src_h) / 2;
                break;

            /* 左上角裁剪 */
            case self::THUMB_NORTHWEST:
                // 计算缩放比例
                if ($src_w > $src_h) {
                    $scale = $dst_h / $src_h;
                } else {
                    $scale = $dst_w / $src_w;
                }

                //设置缩略图的坐标及宽度和高度
                $src_x = $src_y = 0;
                $src_w = $dst_w / $scale;
                $src_h = $dst_h / $scale;
                break;

            /* 右下角裁剪 */
            case self::THUMB_SOUTHEAST:
                // 计算缩放比例
                if ($src_w > $src_h) {
                    $scale = $dst_h / $src_h;
                } else {
                    $scale = $dst_w / $src_w;
                }

                //设置缩略图的坐标及宽度和高度
                $src_w = $dst_w / $scale;
                $src_h = $dst_h / $scale;
                $src_x = $this->info['width'] - $src_w;
                $src_y = $this->info['height'] - $src_h;
                break;

            /* 固定 */
            case self::THUMB_FIXED:
                $src_x = $src_y = 0;
                break;

            /* 尺寸不变 */
            case self::THUMB_NOT:
                $dst_w = $src_w;
                $dst_h = $src_h;
                $src_x = $src_y = 0;
                break;

            default:
                // 不支持的缩略图裁剪类型
                throw new Exception("不支持的缩略图裁剪类型.");
                break;
        }

        $this->crop($src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h);
    }

    /**
     * 添加图片水印
     *
     * @param string $filename 水印图的路径
     * @param int $locate 打水印的位置
     * @param int $alpha 透明度
     * @param array $offset 偏移量
     *
     * @throws Exception
     */
    public function water($filename, $locate = self::WATER_SOUTHEAST, $alpha = 80, $offset = array(0, 0))
    {
        // 获取水印图像信息
        $tmp = getimagesize($filename);
        $info = array(
            'width'  => $tmp[0],
            'height' => $tmp[1],
            'type'   => image_type_to_extension($tmp[2], false),
            'mime'   => $tmp['mime'],
        );

        // 创建水印图像资源
        $func = 'imagecreatefrom' . $info['type'];
        $water_image = $func($filename);

        /* 设定水印位置 */
        switch ($locate) {
            /* 右下角水印 */
            case self::WATER_SOUTHEAST:
                $x = $this->info['width'] - $info['width'];
                $y = $this->info['height'] - $info['height'];
                break;

            /* 左下角水印 */
            case self::WATER_SOUTHWEST:
                $x = 0;
                $y = $this->info['height'] - $info['height'];
                break;

            /* 左上角水印 */
            case self::WATER_NORTHWEST:
                $x = $y = 0;
                break;

            /* 右上角水印 */
            case self::WATER_NORTHEAST:
                $x = $this->info['width'] - $info['width'];
                $y = 0;
                break;

            /* 居中水印 */
            case self::WATER_CENTER:
                $x = ($this->info['width'] - $info['width']) / 2;
                $y = ($this->info['height'] - $info['height']) / 2;
                break;

            /* 下居中水印 */
            case self::WATER_SOUTH:
                $x = ($this->info['width'] - $info['width']) / 2;
                $y = $this->info['height'] - $info['height'];
                break;

            /* 右居中水印 */
            case self::WATER_EAST:
                $x = $this->info['width'] - $info['width'];
                $y = ($this->info['height'] - $info['height']) / 2;
                break;

            /* 上居中水印 */
            case self::WATER_NORTH:
                $x = ($this->info['width'] - $info['width']) / 2;
                $y = 0;
                break;

            /* 左居中水印 */
            case self::WATER_WEST:
                $x = 0;
                $y = ($this->info['height'] - $info['height']) / 2;
                break;

            default:
                /* 自定义水印坐标 */
                if (is_array($locate)) {
                    list($x, $y) = $locate;
                } else {
                    // 不支持的水印位置类型
                    throw new Exception("不支持的水印位置类型.");
                }
        }

        /* 设置偏移量 */
        $x = $x + $offset[0];
        $y = $y + $offset[1];

        // 添加水印
        $tmp_image = imagecreatetruecolor($info['width'], $info['height']);
        $color = imagecolorallocate($tmp_image, 255, 255, 255);
        imagefill($tmp_image, 0, 0, $color);

        imagecopy($tmp_image, $this->_image, 0, 0, $x, $y, $info['width'], $info['height']);
        imagecopy($tmp_image, $water_image, 0, 0, 0, 0, $info['width'], $info['height']);
        imagecopymerge($this->_image, $tmp_image, $x, $y, 0, 0, $info['width'], $info['height'], $alpha);

        // 销毁临时图片资源 + 销毁水印资源
        imagedestroy($tmp_image);
        imagedestroy($water_image);
    }

    /**
     * 添加文字水印
     *
     * @param string $text 作为水印的文字
     * @param string $fontfile 字体路径
     * @param int $fontsize 字号
     * @param string $color 字体颜色
     * @param int $alpha 透明度
     * @param int $locate 水印位置
     * @param array $offset 偏移量
     * @param int $angle 旋转角度
     *
     * @throws Exception
     */
    public function text(
        $text,
        $fontfile,
        $fontsize = 25,
        $color = '#000000',
        $alpha = 80,
        $locate = self::WATER_SOUTHEAST,
        $offset = array(0, 0),
        $angle = 0
    )
    {
        //获取文字信息
        $info = imagettfbbox($fontsize, $angle, $fontfile, $text);
        $minx = min($info[0], $info[2], $info[4], $info[6]);
        $maxx = max($info[0], $info[2], $info[4], $info[6]);
        $miny = min($info[1], $info[3], $info[5], $info[7]);
        $maxy = max($info[1], $info[3], $info[5], $info[7]);

        /* 计算文字初始坐标和尺寸 */
        $x = $minx;
        $y = abs($miny);
        $w = $maxx - $minx;
        $h = $maxy - $miny;

        /* 设定文字位置 */
        switch ($locate) {
            /* 右下角文字 */
            case self::WATER_SOUTHEAST:
                $x += $this->info['width'] - $w;
                $y += $this->info['height'] - $h;
                break;

            /* 左下角文字 */
            case self::WATER_SOUTHWEST:
                $y += $this->info['height'] - $h;
                break;

            /* 左上角文字 */
            case self::WATER_NORTHWEST:
                // 起始坐标即为左上角坐标，无需调整
                break;

            /* 右上角文字 */
            case self::WATER_NORTHEAST:
                $x += $this->info['width'] - $w;
                break;

            /* 居中文字 */
            case self::WATER_CENTER:
                $x += ($this->info['width'] - $w) / 2;
                $y += ($this->info['height'] - $h) / 2;
                break;

            /* 下居中文字 */
            case self::WATER_SOUTH:
                $x += ($this->info['width'] - $w) / 2;
                $y += $this->info['height'] - $h;
                break;

            /* 右居中文字 */
            case self::WATER_EAST:
                $x += $this->info['width'] - $w;
                $y += ($this->info['height'] - $h) / 2;
                break;

            /* 上居中文字 */
            case self::WATER_NORTH:
                $x += ($this->info['width'] - $w) / 2;
                break;

            /* 左居中文字 */
            case self::WATER_WEST:
                $y += ($this->info['height'] - $h) / 2;
                break;

            default:
                /* 自定义文字坐标 */
                if (is_array($locate)) {
                    list($posx, $posy) = $locate;
                    $x += $posx;
                    $y += $posy;
                } else {
                    // 不支持的文字位置类型
                    throw new Exception("不支持的文字位置类型.");
                }
        }

        /* 设置偏移量 */
        $x = $x + $offset[0];
        $y = $y + $offset[1];

        /* 设置颜色 */
        if (is_string($color) && 0 === strpos($color, '#')) {
            $color = str_split(substr($color, 1), 2);
            $color = array_map('hexdec', $color);
        } else {
            if (!is_array($color)) {
                // 错误的颜色值
                throw new Exception("错误的颜色值.");
            }
        }

        /* 写入文字 */
        $col = imagecolorallocatealpha($this->_image, $color[0], $color[1], $color[2], $alpha);
        imagettftext($this->_image, $fontsize, $angle, $x, $y, $col, $fontfile, $text);
    }

    private function setFunc($interlace)
    {
        $type = $this->_type;
        /* 输出目标图像 */
        if ('jpeg' == $type || 'jpg' == $type) {
            imageinterlace($this->_image, $interlace); // jpg设置隔行扫描
            $func = 'imagejpeg';
        } else {
            $func = 'image' . $type;
        }

        return $func;
    }

    /**
     * 图像输出到浏览器
     *
     * @param int $interlace 隔行扫描 jpg
     */
    public function browse($interlace = 1)
    {
        $mime = $this->_mime;
        header("content-type:{$mime}");

        $func = $this->setFunc($interlace);
        $func($this->_image);
    }

    /**
     * 得到图像的字符流
     *
     * @param int $interlace $interlace 隔行扫描 jpg
     *
     * @return string
     */
    public function getContent($interlace = 1)
    {
        $func = $this->setFunc($interlace);

        ob_start();
        $func($this->_image);
        $content = ob_get_clean();

        return $content;
    }

    /**
     * 图像保存到文件
     *
     * @param string $filename 保存的路径
     * @param int $interlace 隔行扫描 jpg
     */
    public function save($filename, $interlace = 1)
    {
        $func = $this->setFunc($interlace);

        $func($this->_image, $filename);
    }
}
